﻿namespace DotNetTracer

open System
open System.IO
open Mono.Cecil
open Mono.Cecil.Cil
open Mono.Cecil.Rocks

module Helpers =
    let getType<'T> (): Type = typedefof<'T>
    let mutableList<'T> () = new System.Collections.Generic.List<'T>()
    let instructionList () = mutableList<Instruction>()

    type ILGenInfo(x,y) = class
        member this.Processor with get(): ILProcessor = x
        member this.Module with get(): ModuleDefinition = y
    end

    let toDict (strm: seq<'k*'v>) = 
        let d = System.Collections.Generic.Dictionary<'k,'v>()
        strm |> Seq.iter (fun (k,v) -> d.Add(k,v))
        d
    
    type ILogger = 
       abstract member BeginAssembly: int -> AssemblyDefinition -> unit
       abstract member BeginModule : int -> ModuleDefinition -> unit
       abstract member EndModule: int -> ModuleDefinition -> unit
       abstract member BeginType: int -> TypeDefinition -> unit
       abstract member EndType: int -> TypeDefinition -> unit
       abstract member BeginMethod: int -> MethodDefinition -> unit
       abstract member EndMethod: int -> MethodDefinition -> unit
       abstract member EndAssembly: int -> AssemblyDefinition -> unit

    type IFilter = 
       abstract member FilterAssembly: AssemblyDefinition -> bool
       abstract member FilterModule : ModuleDefinition -> bool
       abstract member FilterType: TypeDefinition -> bool
       abstract member FilterMethod: MethodDefinition -> bool
    
    type InstrumentationLogger() =
        interface ILogger with
            member this.BeginAssembly(i: int) (def: AssemblyDefinition) = ()
            member this.BeginModule(i: int) (def: ModuleDefinition) = ()
            member this.EndModule(i: int) (def: ModuleDefinition) = ()
            member this.BeginType(i: int) (def: TypeDefinition) = ()
            member this.EndType(i: int) (def: TypeDefinition) = ()
            member this.BeginMethod(i: int) (def: MethodDefinition) = ()
            member this.EndMethod(i: int) (def: MethodDefinition) = ()
            member this.EndAssembly(i: int) (def: AssemblyDefinition) = ()

    type InstrumentationFilter() = 
       interface IFilter with
           member this.FilterAssembly(def: AssemblyDefinition) = true
           member this.FilterModule(def: ModuleDefinition) = true
           member this.FilterType(def: TypeDefinition) = true
           member this.FilterMethod(def: MethodDefinition) = true
               
module AssemblyResolution =
    type AssemblyInfo = {
        MaybeFileName: option<string>
        AssemblyName: Reflection.AssemblyName
    }
    
    let makeAssemblyInfo(assemblyName: Reflection.AssemblyName) = 
        //assert(refAssembly.CodeBase != null)
        let refAssembly = System.Reflection.Assembly.Load(assemblyName)
        let uri = Uri(refAssembly.CodeBase)
        if (uri.IsFile) then //to do: test if the file is on a network drive
            //let host = uri.Host //should be ""
            let path = uri.LocalPath
            {MaybeFileName = Some path; AssemblyName = assemblyName}
        else
            {MaybeFileName = None; AssemblyName = assemblyName}

    let getReferencedAssemblies(assembly: Reflection.Assembly) = 
        assembly.GetReferencedAssemblies()
        |> Array.map makeAssemblyInfo

    let getReferencedAssembliesFromFile (fileName: string): AssemblyInfo [] =
        let assembly = System.Reflection.Assembly.LoadFile(fileName)
        getReferencedAssemblies(assembly)

    let getReferencedAssembliesFromAssemblyName (name: Reflection.AssemblyName): AssemblyInfo [] =
        let assembly = System.Reflection.Assembly.Load(name)
        getReferencedAssemblies(assembly)

    let depthFirstTraversal<'a,'k when 'k: equality> 
                (input: 'a) 
                (getKey: 'a -> 'k)
                (getLinks: 'a -> seq<'a>): 'a[] =
        let visited = System.Collections.Generic.Dictionary<'k,bool>()
        let queue = System.Collections.Generic.Queue<'a>()
        let results = Helpers.mutableList()
        queue.Enqueue(input)
        while queue.Count > 0 do
            let top = queue.Dequeue()
            let key = getKey top
            if not (visited.ContainsKey(key)) then
                results.Add(top)
                visited.Add(key,true)
                for link in getLinks top do
                    queue.Enqueue(link)
        results |> Array.ofSeq
    
    //to do: does not detect cycles
    let topSort<'a,'k when 'k: equality> 
                (input: 'a) 
                (getKey: 'a -> 'k)
                (getLinks: 'a -> seq<'a>): 'a[] =
//        let allWithLinks = 
//            depthFirstTraversal input getKey getLinks
//            |> Array.map(fun link ->
//                            let key = getKey link
//                            let links = getLinks link |> Array.ofSeq
//                            (key, (link, links)))
//            |> Helpers.toDict 
//        let startSet = allWithLinks.Values |> Array.filter(fun (_,links) -> links.Count = 0)
//        if (Array.length startSet = 0) then
//            None
//        else
            let visited = System.Collections.Generic.Dictionary<'key,bool>()
            let results = Helpers.mutableList()
            //after wikideia
            let rec visitNode n = 
                let key = getKey n
                if not(visited.ContainsKey(key)) then
                    visited.Add(key,true)
                    for m in getLinks(n) do
                        visitNode m
                    results.Add(n)
            visitNode input
            results |> Array.ofSeq

    let getAllAssemblies(fileName: string) = 
        let getRefAssembliesLinks(name: string) =
            getReferencedAssembliesFromFile name
            |> Array.choose(fun info -> info.MaybeFileName)
            |> Seq.ofArray
        topSort fileName id getRefAssembliesLinks
                       
module Instructions = 
    open Helpers
    let ldnull (genInfo: ILGenInfo) =
        genInfo.Processor.Create(OpCodes.Ldnull)

    let ldstr (genInfo: ILGenInfo) (s: string) =
        genInfo.Processor.Create(OpCodes.Ldstr, s)

    let ldc_i4 (genInfo: ILGenInfo) (value: int32) =
        genInfo.Processor.Create(OpCodes.Ldc_I4, value)

    let call (genInfo: ILGenInfo) (methodInfo: Reflection.MethodInfo): Instruction = 
        let methodDef = genInfo.Module.Import(methodInfo)
        genInfo.Processor.Create(OpCodes.Call, methodDef)

    let maybeGetCall (instr: Instruction): MethodReference option = 
        if (instr.OpCode = OpCodes.Call) then
            Some (instr.Operand :?> MethodReference)
        else
            None
    let ldarg (genInfo: ILGenInfo) (index: int): Instruction = 
        match index with
         | 0 -> genInfo.Processor.Create(OpCodes.Ldarg_0)
         | 1 -> genInfo.Processor.Create(OpCodes.Ldarg_1)
         | 2 -> genInfo.Processor.Create(OpCodes.Ldarg_2)
         | 3 -> genInfo.Processor.Create(OpCodes.Ldarg_3)
         | _ -> genInfo.Processor.Create(OpCodes.Ldarg_S, (byte)index)
 
    let box (genInfo: ILGenInfo) (t:TypeReference) =
        genInfo.Processor.Create(OpCodes.Box, t)
   
    let nop (genInfo: ILGenInfo) =
        genInfo.Processor.Create(OpCodes.Nop)
    
    let stloc (genInfo: ILGenInfo) (varDef: VariableDefinition) = 
        let index = varDef.Index
        match index with
         | 0 -> genInfo.Processor.Create(OpCodes.Stloc_0)
         | 1 -> genInfo.Processor.Create(OpCodes.Stloc_1)
         | 2 -> genInfo.Processor.Create(OpCodes.Stloc_2)
         | 3 -> genInfo.Processor.Create(OpCodes.Stloc_3)
         | _ -> genInfo.Processor.Create(OpCodes.Stloc_S, (byte)index)
  
    let ldloc (genInfo: ILGenInfo) (varDef: VariableDefinition) = 
        let index = varDef.Index
        match index with
         | 0 -> genInfo.Processor.Create(OpCodes.Ldloc_0)
         | 1 -> genInfo.Processor.Create(OpCodes.Ldloc_1)
         | 2 -> genInfo.Processor.Create(OpCodes.Ldloc_2)
         | 3 -> genInfo.Processor.Create(OpCodes.Ldloc_3)
         | _ -> genInfo.Processor.Create(OpCodes.Ldloc_S, (byte)index)
      
    let leave (genInfo: ILGenInfo) (instr: Instruction) =
        genInfo.Processor.Create(OpCodes.Leave, instr)

    let rethrow (genInfo: ILGenInfo) = 
        genInfo.Processor.Create(OpCodes.Rethrow)
   
    let ret (genInfo: ILGenInfo) = 
        genInfo.Processor.Create(OpCodes.Ret)
   
    let castClass (genInfo: ILGenInfo) (t: TypeReference) = 
        genInfo.Processor.Create(OpCodes.Castclass, t)

    let appendInstructions (arrayOfSeq: seq<Instruction> []) = 
        let newList = instructionList()
        arrayOfSeq
        |> Seq.concat
        |> Seq.iter(fun instr -> newList.Add(instr))
        newList
   
    let clearAllInstructions (genInfo: ILGenInfo) = 
        genInfo.Processor.Body.Instructions |> Array.ofSeq
        |> Seq.iter(fun instr -> genInfo.Processor.Remove(instr))

    let appendAll (genInfo: ILGenInfo)  (newBody: seq<Instruction>) = 
        newBody
        |> Seq.iter(fun instr -> genInfo.Processor.Append(instr))

    let importSystemObject (moduleDef: ModuleDefinition) =
        let t = getType<System.Object>()
        moduleDef.Import(t)

    let importSystemException (moduleDef: ModuleDefinition) =
        let t = getType<System.Exception>()
        moduleDef.Import(t)

    let tryCatch (genInfo: ILGenInfo) (tryInstr: seq<Instruction>) (catchInstr: seq<Instruction>) = 
        let handler = new ExceptionHandler(ExceptionHandlerType.Catch)
        let last (s: seq<'x>) = Array.ofSeq s |> (fun s -> s.[s.Length - 1])
        handler.TryStart <- Seq.head tryInstr
        let nopInstr = nop genInfo
        handler.TryEnd <- Seq.head catchInstr
        handler.HandlerStart <- handler.TryEnd
        handler.HandlerEnd <- last catchInstr
        genInfo.Processor.Body.ExceptionHandlers.Add(handler);
        handler.CatchType <- importSystemObject genInfo.Module

        appendInstructions [|tryInstr; catchInstr;|]

module LoggerRef = 
    open Helpers
    let failIfNull(x: 'a) = 
        if x <> null then x 
        else failwith "null not allowed"

    let loggerType = getType<MyInstrumentation.Logger>()

    let beginMethod = 
        loggerType.GetMethod("beginMethod", [|getType<System.Int32>(); getType<System.String>()|])
        |> failIfNull

    let postParam = 
        loggerType.GetMethod("postObjectParam", [|getType<System.Object>()|])
        |> failIfNull

    let endMethodParams = 
        loggerType.GetMethod("endMethodParams", [||])
        |> failIfNull

    let methodReturn = 
        loggerType.GetMethod("methodReturn", [|getType<System.Object>();getType<System.Int32>()|])
        |> failIfNull

    let methodReturnVoid = 
        loggerType.GetMethod("methodReturnVoid", [|getType<System.Int32>()|])
        |> failIfNull

    let methodThrow = 
        loggerType.GetMethod("methodThrow", [|getType<System.Exception>();getType<System.Int32>()|])
        |> failIfNull

    let disposeAll =
        loggerType.GetMethod("disposeAll", [||])
        |> failIfNull


module Instrumentor = 
    open Helpers
    open Instructions

    //do to: cecil rocks already should have this method
    let getAllTypes (moduleDef: ModuleDefinition): TypeDefinition [] =
        let rec getNestedTypes (t: TypeDefinition) = 
            seq{
                yield! t.NestedTypes
                for n in t.NestedTypes do
                    yield! (getNestedTypes n)
            }
        [|
            yield! moduleDef.Types
            for t in moduleDef.Types do
                yield! (getNestedTypes t)
        |]

    let getMethodDef (path: string) =
        seq{ 
            let assembly = AssemblyDefinition.ReadAssembly(path)
            for moduleDef in assembly.Modules do
                for typeDef in moduleDef.GetAllTypes() do // getAllTypes moduleDef do
                    for methodDef in typeDef.Methods do
                        yield methodDef
           }
    
    let getMethodDefDB (path: string) = 
        getMethodDef path
        |> Seq.map (fun methodDef -> methodDef.FullName, methodDef)
        |> toDict


    let listAssemblyReferences(assembly: AssemblyDefinition) = 
        assembly.Modules
        |> Seq.collect(fun moduleDef -> moduleDef.AssemblyReferences)
        |> Array.ofSeq

    let callImportedMethod (db: System.Collections.Generic.Dictionary<string, MethodDefinition>)
                          (genInfo: ILGenInfo)
                          (methodName: string) = 
        let methodDef = db.[methodName]
        let importedMethod = genInfo.Module.Import(methodDef.GetElementMethod())
        genInfo.Processor.Create(OpCodes.Call, importedMethod)   
    
    type MethodDB = MethodDB of System.Collections.Generic.Dictionary<string, MethodDefinition>

    let patchCalls (methodDef: MethodDefinition) 
                   (patchedAssemblyName: string)
                   (MethodDB libDB) = 
        let gen = ILGenInfo(methodDef.Body.GetILProcessor(), methodDef.Module)
        gen.Processor.Body.Instructions
        |> Array.ofSeq
        |> Seq.iter(fun instr ->
            match maybeGetCall instr with
                | Some d -> 
                    let methodName = d.Resolve().FullName //do to: resolve by method ref (or token), not name
                    let assemblyName = (d.DeclaringType.Resolve().Module.Assembly.Name.Name)
                    if assemblyName = patchedAssemblyName then
                        printfn "replacing *** %s" d.FullName
                        let newcall = callImportedMethod libDB gen methodName 
                            //to do: check if it's just possible to replace the operand
                            //because removed instruction cause issues with jumps
                        //gen.Processor.Replace(instr, newcall)
                        instr.Operand <- newcall.Operand 
                    | _ -> ())
                                       


    let instrumentMethodHelper (isMain: bool) (logId: int) (methodDef: MethodDefinition): MyInstrumentation.LogInfo.Record = 
        (*method (arg1,arg2,...) returns void or non-void
            body1
            ret 
            ...
            ret 

            var methodName = "..."
            beginMethod methodName
            postParam arg1
            postParam arg2
            ...
            endMethodParams
            try
                body1
                result_var = save the result to be returned if method returns non-void
                goto label_new_ret
                ...
                result_var = save the result to be returned if method returns non-void
                goto label_new_ret
            catch(System.Object o)
                methodThrow o
            label_new_ret:
                return result_var
        *)
        let gen = ILGenInfo(methodDef.Body.GetILProcessor(), methodDef.Module)
        let paramInfo = mutableList()

        let maybeSkipInstructions () = 
            if not methodDef.IsConstructor then
                0
            else
                let objectTypeRef = methodDef.DeclaringType.GetElementType()
                let objectType = methodDef.DeclaringType.Resolve()
                let getElementType (tr: TypeReference) =
                    if (tr = null) then null //e.g when taking basetype of system.object
                    else 
                        if tr.IsGenericInstance then (tr :?> GenericInstanceType).ElementType else tr
                let baseType = getElementType objectType.BaseType
                let isBaseConstructor (instr: Instruction) = 
                    if (instr.OpCode = OpCodes.Call) then
                        let mr = instr.Operand :?> MemberReference
                        let tr = mr.DeclaringType
                        let tret = getElementType tr //if tr.IsGenericInstance then (tr :?> GenericInstanceType).ElementType else tr
                        let res = (tret = baseType) || (tret = objectTypeRef) //call to base type or a different constructor
                        res
                    else
                        false
                let maybeIdx = 
                    gen.Processor.Body.Instructions 
                    |> Seq.tryFindIndex(isBaseConstructor)
                match maybeIdx with
                    | Some v -> 1 + v
                    | None -> 0
        
        let origStackSize = gen.Processor.Body.MaxStackSize

        let prolog = instructionList()
        let numSkip = maybeSkipInstructions ()
        gen.Processor.Body.Instructions |> Seq.truncate numSkip |> Array.ofSeq
        |> Seq.iter(fun instr -> prolog.Add(instr)
                                 gen.Processor.Remove(instr))
        if (logId < 100) then
            failwith "fail"
        prolog.Add(ldc_i4 gen logId)
        prolog.Add (ldstr gen methodDef.FullName)  //to do: probably remove that
        prolog.Add(call gen LoggerRef.beginMethod)

        if (methodDef.HasThis) && not(methodDef.IsConstructor) then
           prolog.Add(ldarg gen 0) 
           prolog.Add(call gen LoggerRef.postParam) //post this
           paramInfo.Add(MyInstrumentation.LogInfo.ObjectType) //to do: post the correct type

        let j = if methodDef.HasThis then 1 else 0
        for (i, paramDef) in Seq.mapi (fun i p -> (i + j,p)) methodDef.Parameters do
            if not paramDef.ParameterType.IsByReference then
                //to do: handle by ref. on return properly
                prolog.Add(ldarg gen i) 
                prolog.Add(box gen paramDef.ParameterType)
                prolog.Add(call gen LoggerRef.postParam)
                paramInfo.Add(MyInstrumentation.LogInfo.ObjectType) //to do: post the correct type
            else
                //to do: handle ref. types
                let el = (paramDef.ParameterType :?> ByReferenceType).ElementType
                printfn "not adding %s  %s" ( paramDef.ParameterType.ToString() ) (el.ToString())
                paramInfo.Add(MyInstrumentation.LogInfo.SkippedParam) //to do: post the correct type
        prolog.Add(call gen LoggerRef.endMethodParams)
        //http://stackoverflow.com/questions/4968755/mono-cecil-call-generic-base-class-method-from-other-assembly
        let retType = if methodDef.IsConstructor then 
                            if methodDef.DeclaringType.HasGenericParameters then
                                let genType = GenericInstanceType(methodDef.DeclaringType.GetElementType())
                                for p in methodDef.DeclaringType.GenericParameters do
                                    genType.GenericArguments.Add(p.GetElementType())
                                genType :> TypeReference
                            else
                                methodDef.DeclaringType.GetElementType()
                      else 
                            methodDef.ReturnType
        

        let maybeAddReturnTypeVariable () = 
            if (retType.ToString() <> "System.Void") then //to do: fix without using toString
                let varDef = VariableDefinition(retType)
                gen.Processor.Body.Variables.Add(varDef)
                Some varDef                
            else
                None
        
        let addExceptionVariable () =
            let varDef = VariableDefinition(importSystemException gen.Module)
            gen.Processor.Body.Variables.Add(varDef)
            varDef

        let maybeRetVariable = maybeAddReturnTypeVariable ()
        let exceptionVariable = addExceptionVariable ()
        let labelNewRet = nop gen //to do: replace this with branch instruction if possible

        let replaceRetInstructions () =
            let replaceSingleRetInstruction(retInstr: Instruction) = 
                let newInstr = instructionList()
                match maybeRetVariable with
                    | Some varDef ->
                        if (methodDef.IsConstructor) then
                            newInstr.Add(ldarg gen 0)
                        newInstr.Add(stloc gen varDef)
                    | None -> ()
                newInstr.Add(leave gen labelNewRet)
                retInstr.OpCode <- OpCodes.Nop //cannot remove because other instructions may jump here
                                               //so, just change the opcode
                let temp = ref retInstr
                newInstr |> Seq.iter(fun instr ->
                    gen.Processor.InsertAfter(!temp, instr)
                    temp := instr)
            gen.Processor.Body.Instructions
            |> Seq.filter(fun instr -> instr.OpCode = OpCodes.Ret)
            |> Array.ofSeq
            |> Seq.iter(replaceSingleRetInstruction)
        
        let removeTailInstructions () = 
            gen.Processor.Body.Instructions
            |> Seq.filter(fun instr -> instr.OpCode = OpCodes.Tail)
            |> Array.ofSeq
            |> Seq.iter(fun instr -> gen.Processor.Remove(instr))
        removeTailInstructions () 

        let catchBody = instructionList()
        catchBody.Add(castClass gen (importSystemException gen.Module))
        catchBody.Add(stloc gen exceptionVariable)
        catchBody.Add(ldloc gen exceptionVariable)
        catchBody.Add(ldc_i4 gen logId)
        catchBody.Add(call gen LoggerRef.methodThrow) //ops to do: 
        catchBody.Add(ldloc gen exceptionVariable)
        catchBody.Add(rethrow gen)
        catchBody.Add(nop gen) //why, because Try.End needs to point
        let epilog = instructionList ()
        epilog.Add(labelNewRet)
        match maybeRetVariable with
         | Some retVar -> epilog.Add(ldloc gen retVar)
                          epilog.Add(box gen retType)
                          epilog.Add(ldc_i4 gen logId)
                          epilog.Add(call gen LoggerRef.methodReturn)
                          if isMain then
                            epilog.Add(call gen LoggerRef.disposeAll)
                          if not(methodDef.IsConstructor) then
                            epilog.Add(ldloc gen retVar)
                          paramInfo.Add(MyInstrumentation.LogInfo.ParamType.ObjectType) //to do
         | None -> 
                    epilog.Add(ldc_i4 gen logId)
                    epilog.Add(call gen LoggerRef.methodReturnVoid)
                    paramInfo.Add(MyInstrumentation.LogInfo.ParamType.VoidType) //to do
                    if isMain then
                        epilog.Add(call gen LoggerRef.disposeAll)
        epilog.Add(ret gen)
        replaceRetInstructions ()
        let oldBody = gen.Processor.Body.Instructions |> Array.ofSeq
        clearAllInstructions gen
        let newBody = 
            appendInstructions  [|  prolog;
                                    (tryCatch gen oldBody catchBody);
                                    epilog|]
                            
        appendAll gen newBody
        {   logId = logId; 
            hasThis = methodDef.HasThis;
            shortName = methodDef.Name;
            fullName = methodDef.FullName; 
            expectReturn = true;
            parameterTypes = Array.ofSeq paramInfo}
        
    let instrumentMethod (isMain: bool) (logId: int) (methodDef: MethodDefinition) = 
        methodDef.Body.SimplifyMacros() //replaces things like br.s with br
        let record = instrumentMethodHelper isMain logId methodDef //warning do not use br.s, use br
        methodDef.Body.InitLocals <- true
        methodDef.Body.OptimizeMacros() //replaces things like br with br.s when possible
        record

    let instrumentCalls (startLogId: int) (inputFile: string) (outputFile: string) (logger: ILogger) (filter: IFilter): MyInstrumentation.LogInfo.Record[] = 
        let assembly = AssemblyDefinition.ReadAssembly(inputFile, ReaderParameters(ReadingMode.Deferred))
        
        let mutable logId = startLogId
        let results = mutableList()
        if filter.FilterAssembly(assembly) then
            for moduleDef in assembly.Modules do
                if filter.FilterModule(moduleDef) then
                    logger.BeginModule 0 moduleDef
                    for typeDef in moduleDef.Types do //.GetAllTypes() do //to do: getAllTypes
                        if filter.FilterType(typeDef) then
                            logger.BeginType 0 typeDef
                            for methodDef in typeDef.Methods do
                                if (filter.FilterMethod methodDef) then 
                                    if methodDef.HasBody then //to do: why some methods do not have bodies?
                                        if not (methodDef.Name.Contains(".cctor")) then //to do??
                                            logger.BeginMethod 0 methodDef
                                            let isMain = assembly.EntryPoint = methodDef                                           
                                            results.Add(instrumentMethod isMain logId methodDef)
                                            logId <- logId + 1
                                            logger.EndMethod 0 methodDef
                            logger.EndType 0 typeDef
                    logger.EndModule 0 moduleDef
            let version =  assembly.Name.Version //Version(assembly.Name.Version.Major , assembly.Name.Version.Minor, assembly.Name.Version.MajorRevision,0)
            let nm = new AssemblyNameDefinition(assembly.Name.Name + ".Traced", version)
            //to do: signing an assembly
            assembly.Name <- nm 
            assembly.Write(outputFile)
        results |> Array.ofSeq

    let getAllMethods (moduleDef: ModuleDefinition): seq<MethodDefinition> =
        seq{
            for typeDef in moduleDef.GetAllTypes() do // getAllTypes moduleDef do
                for methodDef in typeDef.Methods do
                    yield methodDef
        }

    let patchAssemblyRef (replacements: System.Collections.Generic.Dictionary<string,
                                                                              AssemblyNameReference*AssemblyNameReference*
                                                                              MethodDB
                                                                              >) 
                         (fileName: string) =
        //it is not enough to replace the reference to assembly, but need to replace reference to function calls
        let assembly = AssemblyDefinition.ReadAssembly(fileName, ReaderParameters(ReadingMode.Immediate))
      
        for modDef in assembly.Modules do
            let toBeReplaced = 
                modDef.AssemblyReferences 
                |>Seq.choose(fun assemblyRef ->
                                let ok,v = replacements.TryGetValue(assemblyRef.Name) //to do: patch by full name, not by name
                                if ok then
                                    let (_,newRef,methods) = v
                                    Some (assemblyRef, newRef,methods)
                                else
                                    None)
                |> Array.ofSeq
            toBeReplaced 
            |> Seq.iter(fun (oldRef,newRef,methodDB) ->
                            printfn "In %s %s --> %s"  fileName oldRef.FullName newRef.FullName
                            let copyRef = modDef.AssemblyReferences |> Array.ofSeq
                            let idx =  copyRef |> Seq.findIndex(fun someRef ->someRef = oldRef)
                            let lastHalf = Array.sub copyRef idx (copyRef.Length - idx)
                            lastHalf |> Seq.iter(fun assRef -> modDef.AssemblyReferences.Remove(assRef)|>ignore)
                            modDef.AssemblyReferences.Add(newRef)
                            lastHalf |> Seq.skip 1 |> Seq.iter(fun assRef ->  modDef.AssemblyReferences.Add(assRef))

                            modDef.AssemblyReferences
                            |> Seq.iter(fun assRef -> printfn "New ref: %s" assRef.Name)
                            printfn "---"
                            // (newRef)
                            
//                            for methodDef in getAllMethods(modDef) do
//                                if methodDef.HasBody then
//                                    patchCalls methodDef oldRef.Name methodDB
                            //modDef.AssemblyReferences.Remove(oldRef) |> ignore
                            )
        //to do: check if this is a problem
        assembly.Write(fileName)

    let instrumentRecursively (inputFile: string) (outputDirectory: string)
                              //(logger: ILogger) (filter: IFilter) 
                              (promptOnExitingPath: string -> bool)
                              = 
        //list of all assemblies sorted toplogically
        
        if not (System.IO.Directory.Exists(outputDirectory)) then
            failwith "Directory does not exist"
        let newPathSuffix = ".Traced"
        let removeMsCorLib = Array.filter(fun name -> let shortName = System.IO.Path.GetFileName(name)
                                                      shortName <> "mscorlib.dll" 
                                                         && shortName <> "Logger.dll" )
        let sortedAssemblies = AssemblyResolution.getAllAssemblies inputFile
                               |> removeMsCorLib  //mscorlib should be instrumented differently, in particular constructors
                               |> Array.filter(fun name -> //tmp patch while developing
                                                    name.EndsWith(".exe") ||
                                                        System.IO.Path.GetFileName(name) = "FSharp.Core.dll")                    
        let filter = InstrumentationFilter()
        let logger = InstrumentationLogger()

        let makeNewAssemblyName (startLogId: int) (fileName: string) = 
            let localFileNameWithoutExt = System.IO.Path.GetFileNameWithoutExtension(fileName)
            let ext = System.IO.Path.GetExtension(fileName)
            let newPath = System.IO.Path.Combine(outputDirectory, localFileNameWithoutExt + newPathSuffix + ext)
            if File.Exists(newPath) then
                if not (promptOnExitingPath newPath) then
                    failwith "overwrite of file was cancelled"
            
            let records = instrumentCalls startLogId fileName newPath logger filter
            ((fileName, newPath), records)
            
        let newNamesList = mutableList()
        let recordsList = mutableList()
        let mutable offset = MyInstrumentation.LogInfo.getStartIndex()
        for name in sortedAssemblies do
            let (names, records) = makeNewAssemblyName offset name
            offset <- offset + records.Length
            newNamesList.Add(names)
            recordsList.AddRange(records)

//                             |> Array.fold(fun (res,records,offset) newName -> makeNewAssemblyName offset newName)
//                                              (("",""),[||], 0)
        let newNames = Array.ofSeq newNamesList    
        let getAssemblyReference(inputFile: string) = 
            let assemblyDef = AssemblyDefinition.ReadAssembly(inputFile).Name
            let assemblyRef = AssemblyNameReference(assemblyDef.Name, assemblyDef.Version)            
            assemblyRef

        let assemblyReplacements = newNames 
                                   |> Array.map (fun (i,o) -> let ri,ro = getAssemblyReference i, getAssemblyReference o
                                                              let methodDB = System.Collections.Generic.Dictionary<_,_>() // getMethodDefDB o
                                                              ri.Name, (ri,ro, MethodDB methodDB)) //to do: patch by full name, not by name
                                   |> Helpers.toDict
        let recordsFileName = System.IO.Path.Combine([|outputDirectory; "records.bin"|])
        MyInstrumentation.LogInfo.recordsToFile recordsFileName recordsList

        newNames
        |> Array.map snd 
        |> Array.iter (patchAssemblyRef assemblyReplacements)       

        let copyLogger () = 
            let loggerPath = @"D:\DotNetTracer\DotNetTracer\Logger\bin\Debug\Logger.dll" //to do
            let loggerFileName = System.IO.Path.GetFileName(loggerPath)
            let newPath = System.IO.Path.Combine(outputDirectory, loggerFileName)
            System.IO.File.Copy(loggerPath, newPath)
        copyLogger ()

//let getAssemblyPaths () = 
//     [| @"D:\Program Files (x86)\Reference Assemblies\Microsoft\FSharp\2.0\Runtime\v4.0\"
//
//     |]
//
//let maybeGetAssemblyName(name: string) = 
//    let parameters = ReaderParameters(ReadingMode.Deferred)
//    try
//        let assembly = AssemblyDefinition.ReadAssembly(name, parameters)
//        Some assembly.Name.Name //to do (inlcude clr version etc)
//    with _ ->
//        None
//
//let isAssembly (name: string) = 
//    match (maybeGetAssemblyName name) with
//     | Some _ -> true
//     | None -> false
//
//let listFilesWithFilter (fileFilter: string -> bool) (dir: string) : string[] = 
//    let rec loop(di: System.IO.DirectoryInfo): seq<string> = 
//        seq{
//            yield! (di.EnumerateFiles() 
//                    |> Seq.map(fun fi -> fi.FullName)
//                    |> Seq.filter(fun name -> fileFilter name))
//            for ndi in di.EnumerateDirectories() do
//                yield! loop ndi
//        }
//    loop (System.IO.DirectoryInfo dir)
//    |> Array.ofSeq   
//
//
//let buildAssemblyDB () = 
//    getAssemblyPaths ()
//    |> Seq.collect (listFilesWithFilter (fun _ -> true))
//    |> Seq.choose (fun fileName ->
//                           match maybeGetAssemblyName fileName with
//                            | Some assemblyName -> Some(assemblyName,fileName)
//                            | None -> None)
//    |> toDict                     
//
//let assemblyDB = buildAssemblyDB()
//
//
//let resolveAssemblyRef (fileName: string) =
//    AssemblyDefinition.ReadAssembly(fileName)
//    |> Instrumentor.listAssemblyReferences 
//    |> Array.choose (fun ref -> 
//        printfn "--> %s" ref.Name
//        if assemblyDB.ContainsKey(ref.Name) then
//            Some assemblyDB.[ref.Name]
//        else None)
//
//
//let patchAssemblyRef (fileName: string) (outputPath: string) =
//    let assembly = AssemblyDefinition.ReadAssembly(fileName)
//    let newDefAss = AssemblyDefinition.ReadAssembly(libPath).Name
//    let newRefAss = AssemblyNameReference(newDefAss.Name, newDefAss.Version)
//    
//    for modDef in assembly.Modules do
//        let assRef = modDef.AssemblyReferences 
//                     |> Seq.filter(fun assRef -> assRef.Name = "FSharp.Core") 
//                     |> Seq.head
//        modDef.AssemblyReferences.Remove(assRef) |> ignore
//        modDef.AssemblyReferences.Add(newRefAss)
//
//    assembly.Write(outputPath)
//    //|> Instrumentor.listAssemblyReferences 
//patchAssemblyRef inputPath outputPath
//printfn "----------------"
//resolveAssemblyRef outputPath


